#include "my_ringbuffer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"

#define BUF_IS_VALID(b) ((b) != NULL && (b)->buff != NULL && (b)->size > 0)
#define BUF_MIN(x, y) ((x) < (y) ? (x) : (y))
#define BUF_MAX(x, y) ((x) > (y) ? (x) : (y))

static SemaphoreHandle_t readMutex = NULL;
static SemaphoreHandle_t writeMutex = NULL;

static const char *TAG = "RB";

static size_t get_read_idx(lwrb_t *rb)
{
    size_t res;
    xSemaphoreTake(readMutex, portMAX_DELAY);
    res = rb->r;
    xSemaphoreGive(readMutex);
    return res;
}

static void store_read_idx(lwrb_t *rb, size_t res)
{
    xSemaphoreTake(readMutex, portMAX_DELAY);
    rb->r = res;
    xSemaphoreGive(readMutex);
}

static size_t get_write_idx(lwrb_t *rb)
{
    size_t res;
    xSemaphoreTake(writeMutex, portMAX_DELAY);
    res = rb->w;
    xSemaphoreGive(writeMutex);
    return res;
}

static void store_write_idx(lwrb_t *rb, size_t res)
{
    xSemaphoreTake(writeMutex, portMAX_DELAY);
    rb->w = res;
    xSemaphoreGive(writeMutex);
}

static void mutex_create(void)
{
    /* 创建互斥信号量 */
    readMutex = xSemaphoreCreateMutex();
    if (readMutex == NULL)
    {
        ESP_LOGI(TAG, "readMutex Create failed");
    }

    writeMutex = xSemaphoreCreateMutex();
    if (writeMutex == NULL)
    {
        ESP_LOGI(TAG, "writeMutex Create failed");
    }
}

static size_t lwrb_get_free(const lwrb_t *rb)
{
    size_t size, w, r;

    if (!BUF_IS_VALID(rb))
    {
        return 0;
    }
    w = get_write_idx((const lwrb_t *)rb);
    r = get_read_idx((const lwrb_t *)rb);

    if (w == r)
    {
        size = rb->size;
    }
    else if (r > w)
    {
        size = r - w;
    }
    else
    {
        size = rb->size - (w - r);
    }

    /* Buffer free size is always 1 less than actual size */
    return size - 1;
}

static size_t lwrb_get_full(const lwrb_t *rb)
{
    size_t size, w, r;

    if (!BUF_IS_VALID(rb))
    {
        return 0;
    }

    /*
     * Copy buffer pointers to local variables.
     *
     * To ensure thread safety (only when in single-entry, single-exit FIFO mode use case),
     * it is important to write buffer r and w values to local w and r variables.
     *
     * Local variables will ensure below if statements will always use the same value,
     * even if buff->w or buff->r get changed during interrupt processing.
     *
     * They may change during load operation, important is that
     * they do not change during if-elseif-else operations following these assignments.
     *
     * lwrb_get_full is only called for read purpose, and when in FIFO mode, then:
     * - buff->r pointer will not change by another process/interrupt because we are in read mode just now
     * - buff->w pointer may change by another process. If it gets changed after buff->w has been loaded to local variable,
     *    buffer will see "full size" less than it really is. This is not a problem, application can
     *    always try again to read more data from remaining full memory that was written just during copy operation
     */
    w = get_write_idx((const lwrb_t *)rb);
    r = get_read_idx((const lwrb_t *)rb);

    if (w == r)
    {
        size = 0;
    }
    else if (w > r)
    {
        size = w - r;
    }
    else
    {
        size = rb->size - (r - w);
    }
    return size;
}

size_t lwrb_write(lwrb_t *rb, const void *data, size_t btw)
{
    size_t tocopy, free, buff_w_ptr;
    const uint8_t *d = data;

    if (!BUF_IS_VALID(rb) || data == NULL || btw == 0 || btw > rb->size)
    {
        return 0;
    }

    /* Calculate maximum number of bytes available to write */
    free = lwrb_get_free(rb);
    btw = BUF_MIN(free, btw);
    if (btw == 0)
    {
        return 0;
    }
    buff_w_ptr = get_write_idx(rb);

    /* Step 1: Write data to linear part of buffer */
    tocopy = BUF_MIN(rb->size - buff_w_ptr, btw);
    memcpy(&rb->buff[buff_w_ptr], d, tocopy);
    buff_w_ptr += tocopy;
    btw -= tocopy;

    /* Step 2: Write data to beginning of buffer (overflow part) */
    if (btw > 0)
    {
        memcpy(rb->buff, &d[tocopy], btw);
        buff_w_ptr = btw;
    }

    //  为什么会触发这个
    /* Step 3: Check end of buffer */
    if (buff_w_ptr >= rb->size)
    {
        buff_w_ptr = 0;
    }
    /*
     * Write final value to the actual running variable.
     * This is to ensure no read operation can access intermediate data
     */
    store_write_idx(rb, buff_w_ptr);
    return tocopy + btw;
}

size_t lwrb_read(lwrb_t *rb, void *data, size_t btr)
{
    size_t tocopy, full, buff_r_ptr;
    uint8_t *d = data;

    if (!BUF_IS_VALID(rb) || data == NULL || btr == 0)
    {
        return 0;
    }

    /* Calculate maximum number of bytes available to read */
    full = lwrb_get_full(rb);
    btr = BUF_MIN(full, btr);
    if (btr == 0)
    {
        return 0;
    }
    buff_r_ptr = get_read_idx(rb);

    /* Step 1: Read data from linear part of buffer */
    tocopy = BUF_MIN(rb->size - buff_r_ptr, btr);
    memcpy(d, &rb->buff[buff_r_ptr], tocopy);
    buff_r_ptr += tocopy;
    btr -= tocopy;

    /* Step 2: Read data from beginning of buffer (overflow part) */
    if (btr > 0)
    {
        memcpy(&d[tocopy], rb->buff, btr);
        buff_r_ptr = btr;
    }
    //  为什么会触发这个
    /* Step 3: Check end of buffer */
    if (buff_r_ptr >= rb->size)
    {
        buff_r_ptr = 0;
    }

    /*
     * Write final value to the actual running variable.
     * This is to ensure no write operation can access intermediate data
     */
    store_read_idx(rb, buff_r_ptr);
    return tocopy + btr;
}
/**
 * \brief           Read from buffer without changing read pointer (peek only)
 * \param[in]       buff: Buffer handle
 * \param[in]       skip_count: Number of bytes to skip before reading data
 * \param[out]      data: Pointer to output memory to copy buffer data to
 * \param[in]       btp: Number of bytes to peek
 * \return          Number of bytes peeked and written to output array
 */
size_t lwrb_peek(const lwrb_t *rb, void *data, size_t btp)
{
    size_t full, tocopy, r;
    uint8_t *d = data;

    if (!BUF_IS_VALID(rb) || data == NULL || btp == 0)
    {
        return 0;
    }

    /*
     * Calculate maximum number of bytes available to read
     * and check if we can even fit to it
     */
    full = lwrb_get_full(rb);

    /* Check maximum number of bytes available to read after skip */
    btp = BUF_MIN(full, btp);
    if (btp == 0)
    {
        return 0;
    }

    /* Step 1: Read data from linear part of buffer */
    r = get_read_idx(rb);
    tocopy = BUF_MIN(rb->size - r, btp);
    memcpy(d, &rb->buff[r], tocopy);
    btp -= tocopy;

    /* Step 2: Read data from beginning of buffer (overflow part) */
    if (btp > 0)
    {
        memcpy(&d[tocopy], rb->buff, btp);
    }
    return tocopy + btp;
}

int8_t lwrb_init(lwrb_t *rb, void *buffdata, size_t size)
{
    mutex_create();
    if (rb == NULL || buffdata == NULL || size == 0)
    {
        return 0;
    }
    rb->size = size;
    rb->buff = buffdata;
    rb->r = 0;
    rb->w = 0;
    return 1;
}
